package kd.xkqj.unittest.plugin;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import kd.xkqj.unittest.plugin.model.JavaFileInfo;
import kd.xkqj.unittest.plugin.model.JavaMethodInfo;
import kd.xkqj.unittest.plugin.setting.PluginSettingsService;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static kd.xkqj.unittest.plugin.action.AnActionHelper.showNotification;
import static kd.xkqj.unittest.plugin.model.JavaFileInfo.isExistTargetMethod;

/**
 * @description: 代码生成器
 * @author feng_jun_peng
 * @create 2024-12-19 11:06 
 * @version 1.0
 */
public class CodeGenerator {
    /**
     * description: 生成新的单元测试文件
     * @author: feng_jun_peng
     * @create: 2024-12-19 11:20
     * @param targetPath: 目标路径
     * @param sourceJavaFileInfo: 源代码的文件信息
     * @param newClassName: 单元测试类名
     * @return: void
     */
    public static void genNewUnitTestFile(String targetPath, JavaFileInfo sourceJavaFileInfo, String newClassName) {
        String templateContent = "package ${PACKAGE_NAME};\n\n" +
                "${IMPORT_PACKAGES}\n" +
                "${IMPORT_TEST_CORE}\n" +
                "${FORM_ANNOTATIONS}\n" +
                "public class ${CLASS_NAME} ${EXTEND_CLASS}{\n\n" +
                "${METHODS}" +
                "}";

        // 读取配置
        PluginSettingsService settingsService = PluginSettingsService.getInstance();
        var state = settingsService.getState();

        if (newClassName.indexOf(".java") != -1) {
            newClassName = newClassName.replace(".java", "");
        }

        String classContent = templateContent
                .replace("${PACKAGE_NAME}", sourceJavaFileInfo.getPackageName())
                .replace("${CLASS_NAME}", newClassName)
                .replace("${IMPORT_PACKAGES}", state.importPackages);

        if (state.autoExtendPlugin && !sourceJavaFileInfo.getSuperclass().trim().isEmpty()) {
            List<String> extendClassNames = new ArrayList<>(Arrays.asList("AbstractBasePlugIn", "AbstractBillPlugIn", "AbstractFormPlugin", "AbstractListPlugin", "AbstractMobFormPlugin", "AbstractMobListPlugin", "AbstractPlmMobFormPlugin", "AbstractPlmShowMultiChoosePlugin", "AbstractPlmSmBasePlugin", "AbstractPlmSmBillPlugin", "AbstractPlmSmFormPlugin", "AbstractPlmSmListPlugin", "AbstractPlmSmMobListPlugin", "AbstractPlmSmReportListDataPlugin", "AbstractPlmSmReportPlugin", "AbstractPlmSmStandardTreeListPlugin", "AbstractPlmSmTreeListPlugin", "AbstractReportFormPlugin", "AbstractReportListDataPlugin", "AbstractTreeListPlugin", "AbstractUpgradeFormPlugin", "DynamicEntityPlugin", "SelectRowEntryGridPlugin", "StandardTreeListPlugin"));

            if (extendClassNames.contains(sourceJavaFileInfo.getSuperclass())) {
                classContent = classContent
                        .replace("${EXTEND_CLASS}", "extends AbstractJUnitTestPlugIn ");

                classContent = classContent
                        .replace("${FORM_ANNOTATIONS}", "@Form(formName = \"\", type = FORMTYPE.FORM)");
            }
        }
        classContent = classContent.replace("${EXTEND_CLASS}", "")
                .replace("${FORM_ANNOTATIONS}", "");

        StringBuilder sb = new StringBuilder();

        int level = 0;
        // 寻找上级目录中，是否有core包
        if (state.autoGenJWYTest) {
            var path = Paths.get(targetPath).getParent();
            var tempPackageName = sourceJavaFileInfo.getPackageName();
            var findCore = false;
            while (!"java".equals(path.getFileName().toString()) && !findCore && level <= 20) {
                level++;
                for (String tempPath : new File(path.toString()).list()) {
                    if ("core".equals(tempPath)) {
                        findCore = true;
                        break;
                    }
                }
                if (findCore)
                    break;
                if (tempPackageName.indexOf(".") == -1) {
                    break;
                }
                tempPackageName = tempPackageName.substring(0, tempPackageName.lastIndexOf("."));
                path = path.getParent();
            }

            if (findCore) {
                // 尝试寻找core文件夹目录
                classContent = classContent.replace("${IMPORT_TEST_CORE}", "import " + tempPackageName + ".core.*;\n");
            }
            sb.append("\t@Before\n" +
                    "\tpublic void init() { TestHelper.init(); }\n\n" +
                    "\t@After\n" +
                    "\tpublic void release() { TestHelper.reset(); }\n\n");
        }
        classContent = classContent.replace("${IMPORT_TEST_CORE}", "");

        if (state.genPublic) {
            for (JavaMethodInfo methodInfo : sourceJavaFileInfo.getPublicMethodInfos()) {
                sb.append(genTestMethodText(methodInfo.getMethodName()));
            }
        }
        if (state.genProtected) {
            for (JavaMethodInfo methodInfo : sourceJavaFileInfo.getProtectedMethodInfos()) {
                sb.append(genTestMethodText(methodInfo.getMethodName()));
            }
        }
        if (state.genPrivate) {
            for (JavaMethodInfo methodInfo : sourceJavaFileInfo.getPrivateMethodInfos()) {
                sb.append(genTestMethodText(methodInfo.getMethodName()));
            }
        }

        classContent = classContent.replace("${METHODS}", sb.toString());

        try {
            // 写入内容，指定UTF-8编码
            Files.write(Paths.get(targetPath), classContent.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * description: 按配置补全所有待测试方法
     * @author: feng_jun_peng
     * @create: 2024-12-19 11:27
     * @param targetPath: 单元测试文件路径
     * @param sourceJavaFileInfo: 源代码文件信息
     * @param targetJavaFileInfo: 测试代码文件信息
     * @return: void
     */
    public static void completeAllTestMethod(Project project, String targetPath, JavaFileInfo sourceJavaFileInfo, JavaFileInfo targetJavaFileInfo) {
        // 读取配置
        PluginSettingsService settingsService = PluginSettingsService.getInstance();
        var state = settingsService.getState();

        List<String> newMethods = new ArrayList<>();
        if (state.genPublic)
            getNewMethods(sourceJavaFileInfo.getPublicMethodInfos(), targetJavaFileInfo.getPublicMethodInfos(), newMethods);
        if (state.genProtected)
            getNewMethods(sourceJavaFileInfo.getProtectedMethodInfos(), targetJavaFileInfo.getPublicMethodInfos(), newMethods); // 由于单元测试中，方法都是公共的，因此比对的目标方法也是公共
        if (state.genPrivate)
            getNewMethods(sourceJavaFileInfo.getPrivateMethodInfos(), targetJavaFileInfo.getPublicMethodInfos(), newMethods); // 由于单元测试中，方法都是公共的，因此比对的目标方法也是公共

        if (newMethods.size() > 0) {
            appendNewMethodsToTestFile(project, targetPath, newMethods);
        } else {
            showNotification(project, "提醒", "没有找到新的测试方法", 3000);
        }
    }

    /**
     * description: 补充1个指定测试方法
     * @author: feng_jun_peng
     * @create: 2024-12-19 12:11
     * @param targetPath: 测试文件路径
     * @param newMethodName: 测试方法名
     * @return: void
     */
    public static void completeOneTestMethod(Project project, String targetPath, String newMethodName) {
        // 读取配置
        PluginSettingsService settingsService = PluginSettingsService.getInstance();
        var state = settingsService.getState();
        appendNewMethodsToTestFile(project, targetPath, new ArrayList<>(Arrays.asList(newMethodName)));
    }

    /**
     * description: 追加字符串到测试文件
     * @author: feng_jun_peng
     * @create: 2024-12-19 12:08
     * @param targetPath: 测试文件路径
     * @param methods: 新的方法名
     * @return: void
     */
    private static void appendNewMethodsToTestFile(Project project, String targetPath, List<String> methods) {
        try {
            var classContent = Files.readString(Path.of(targetPath));
            var index = classContent.lastIndexOf("}");
            if (index != -1) {
                classContent = classContent.substring(0, index); //去掉后面的反括号
            }
            StringBuilder sb = new StringBuilder();
            for (String method : methods) {
                sb.append(genTestMethodText(method));
            }
            classContent = classContent + sb.toString() + "}";
            Files.write(Paths.get(targetPath), classContent.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);

            showNotification(project, "已补充测试方法", String.join(", ", methods), methods.size() > 1 ? 5000 : 3000);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * description: 生成测试方法文本
     * @author: feng_jun_peng
     * @create: 2024-12-19 11:18
     * @param methodName: 方法名，会自动在前面加上test前缀
     * @return: java.lang.String
     */
    private static String genTestMethodText(String methodName) {
        PluginSettingsService settingsService = PluginSettingsService.getInstance();
        var state = settingsService.getState();

        StringBuilder sb = new StringBuilder();
        sb.append("\t@Test\n");
        if (state.addDisplayName) {
            sb.append(String.format("\t@DisplayName(\"测试 %s\")\n", methodName));
        }
        if (state.addNOClientMock) {
            sb.append(String.format("\t@NOClientMock\n"));
        }
        if (state.addCustomParams) {
            sb.append(String.format("\t@CustomParams(customParams = \"\")\n"));
        }

        sb.append(String.format("\tpublic void test%s() {\n", methodName.substring(0, 1).toUpperCase() + methodName.substring(1)));

        if (state.addUnsupportedEx) {
            sb.append("\t\tthrow new UnsupportedOperationException();");
        }

        sb.append("\n\t}\n\n");
        return sb.toString();
    }

    /**
     * description: 获取新增的方法集合
     * @author: feng_jun_peng
     * @create: 2024-12-19 10:51
     * @param sources: 代码源文件的方法集合
     * @param targets: 测试文件的方法集合
     * @param newMethods: 新的方法集合
     * @return: void
     */
    private static void getNewMethods(List<JavaMethodInfo> sources, List<JavaMethodInfo> targets, List<String> newMethods) {
        for (JavaMethodInfo methodInfo : sources) {
            if (!isExistTargetMethod(targets, "test" + methodInfo.getMethodName())) {
                newMethods.add(methodInfo.getMethodName());
            }
        }
    }
}
